tree-sitter-analyzer 0.9.3__py3-none-any.whl → 0.9.4__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.

Files changed (32) hide show
  1. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  2. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  3. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  4. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  5. tree_sitter_analyzer/cli/info_commands.py +121 -121
  6. tree_sitter_analyzer/cli_main.py +307 -307
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/cache_service.py +5 -4
  9. tree_sitter_analyzer/core/query.py +502 -502
  10. tree_sitter_analyzer/encoding_utils.py +6 -2
  11. tree_sitter_analyzer/exceptions.py +400 -406
  12. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  13. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  14. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  15. tree_sitter_analyzer/language_detector.py +398 -398
  16. tree_sitter_analyzer/language_loader.py +224 -224
  17. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  18. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  19. tree_sitter_analyzer/mcp/server.py +30 -9
  20. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  21. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  22. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  23. tree_sitter_analyzer/models.py +470 -470
  24. tree_sitter_analyzer/security/__init__.py +22 -22
  25. tree_sitter_analyzer/security/boundary_manager.py +243 -243
  26. tree_sitter_analyzer/security/regex_checker.py +297 -292
  27. tree_sitter_analyzer/table_formatter.py +703 -652
  28. tree_sitter_analyzer/utils.py +50 -19
  29. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/METADATA +1 -1
  30. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/RECORD +32 -32
  31. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/WHEEL +0 -0
  32. {tree_sitter_analyzer-0.9.3.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/entry_points.txt +0 -0
@@ -1,502 +1,502 @@
1
- #!/usr/bin/env python3
2
- """
3
- Query module for tree_sitter_analyzer.core.
4
-
5
- This module provides the QueryExecutor class which handles Tree-sitter
6
- query execution in the new architecture.
7
- """
8
-
9
- import logging
10
- import time
11
- from typing import Any
12
-
13
- from tree_sitter import Language, Node, Tree
14
-
15
- from ..query_loader import get_query_loader
16
-
17
- # Configure logging
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class QueryExecutor:
22
- """
23
- Tree-sitter query executor for the new architecture.
24
-
25
- This class provides a unified interface for executing Tree-sitter queries
26
- with proper error handling and result processing.
27
- """
28
-
29
- def __init__(self) -> None:
30
- """Initialize the QueryExecutor."""
31
- self._query_loader = get_query_loader()
32
- self._execution_stats: dict[str, Any] = {
33
- "total_queries": 0,
34
- "successful_queries": 0,
35
- "failed_queries": 0,
36
- "total_execution_time": 0.0,
37
- }
38
- logger.info("QueryExecutor initialized successfully")
39
-
40
- def execute_query(
41
- self,
42
- tree: Tree | None,
43
- language: Language,
44
- query_name: str,
45
- source_code: str,
46
- ) -> dict[str, Any]:
47
- """
48
- Execute a predefined query by name.
49
-
50
- Args:
51
- tree: Tree-sitter tree to query
52
- language: Tree-sitter language object
53
- query_name: Name of the predefined query
54
- source_code: Source code for context
55
-
56
- Returns:
57
- Dictionary containing query results and metadata
58
- """
59
- start_time = time.time()
60
- self._execution_stats["total_queries"] += 1
61
-
62
- try:
63
- # Validate inputs
64
- if tree is None:
65
- return self._create_error_result("Tree is None", query_name=query_name)
66
-
67
- if language is None:
68
- return self._create_error_result( # type: ignore[unreachable]
69
- "Language is None", query_name=query_name
70
- )
71
-
72
- # Get the query string
73
- language_name = language.name if hasattr(language, "name") else "unknown"
74
- query_string = self._query_loader.get_query(language_name, query_name)
75
- if query_string is None:
76
- return self._create_error_result(
77
- f"Query '{query_name}' not found", query_name=query_name
78
- )
79
-
80
- # Create and execute the query
81
- try:
82
- query = language.query(query_string)
83
- captures = query.captures(tree.root_node)
84
-
85
- # Process captures
86
- try:
87
- processed_captures = self._process_captures(captures, source_code)
88
- except Exception as e:
89
- logger.error(f"Error processing captures for {query_name}: {e}")
90
- return self._create_error_result(
91
- f"Capture processing failed: {str(e)}", query_name=query_name
92
- )
93
-
94
- self._execution_stats["successful_queries"] += 1
95
- execution_time = time.time() - start_time
96
- self._execution_stats["total_execution_time"] += execution_time
97
-
98
- return {
99
- "captures": processed_captures,
100
- "query_name": query_name,
101
- "query_string": query_string,
102
- "execution_time": execution_time,
103
- "success": True,
104
- }
105
-
106
- except Exception as e:
107
- logger.error(f"Error executing query '{query_name}': {e}")
108
- return self._create_error_result(
109
- f"Query execution failed: {str(e)}", query_name=query_name
110
- )
111
-
112
- except Exception as e:
113
- logger.error(f"Unexpected error in execute_query: {e}")
114
- self._execution_stats["failed_queries"] += 1
115
- return self._create_error_result(
116
- f"Unexpected error: {str(e)}", query_name=query_name
117
- )
118
-
119
- def execute_query_string(
120
- self,
121
- tree: Tree | None,
122
- language: Language,
123
- query_string: str,
124
- source_code: str,
125
- ) -> dict[str, Any]:
126
- """
127
- Execute a query string directly.
128
-
129
- Args:
130
- tree: Tree-sitter tree to query
131
- language: Tree-sitter language object
132
- query_string: Query string to execute
133
- source_code: Source code for context
134
-
135
- Returns:
136
- Dictionary containing query results and metadata
137
- """
138
- start_time = time.time()
139
- self._execution_stats["total_queries"] += 1
140
-
141
- try:
142
- # Validate inputs
143
- if tree is None:
144
- return self._create_error_result("Tree is None")
145
-
146
- if language is None:
147
- return self._create_error_result("Language is None") # type: ignore[unreachable]
148
-
149
- # Create and execute the query
150
- try:
151
- query = language.query(query_string)
152
- captures = query.captures(tree.root_node)
153
-
154
- # Process captures
155
- try:
156
- processed_captures = self._process_captures(captures, source_code)
157
- except Exception as e:
158
- logger.error(f"Error processing captures: {e}")
159
- return self._create_error_result(
160
- f"Capture processing failed: {str(e)}"
161
- )
162
-
163
- self._execution_stats["successful_queries"] += 1
164
- execution_time = time.time() - start_time
165
- self._execution_stats["total_execution_time"] += execution_time
166
-
167
- return {
168
- "captures": processed_captures,
169
- "query_string": query_string,
170
- "execution_time": execution_time,
171
- "success": True,
172
- }
173
-
174
- except Exception as e:
175
- logger.error(f"Error executing query string: {e}")
176
- return self._create_error_result(
177
- f"Query execution failed: {str(e)}", query_string=query_string
178
- )
179
-
180
- except Exception as e:
181
- logger.error(f"Unexpected error in execute_query_string: {e}")
182
- self._execution_stats["failed_queries"] += 1
183
- return self._create_error_result(f"Unexpected error: {str(e)}")
184
-
185
- def execute_multiple_queries(
186
- self, tree: Tree, language: Language, query_names: list[str], source_code: str
187
- ) -> dict[str, dict[str, Any]]:
188
- """
189
- Execute multiple queries and return combined results.
190
-
191
- Args:
192
- tree: Tree-sitter tree to query
193
- language: Tree-sitter language object
194
- query_names: List of query names to execute
195
- source_code: Source code for context
196
-
197
- Returns:
198
- Dictionary mapping query names to their results
199
- """
200
- results = {}
201
-
202
- for query_name in query_names:
203
- result = self.execute_query(tree, language, query_name, source_code)
204
- results[query_name] = result
205
-
206
- return results
207
-
208
- def _process_captures(
209
- self, captures: Any, source_code: str
210
- ) -> list[dict[str, Any]]:
211
- """
212
- Process query captures into standardized format.
213
-
214
- Args:
215
- captures: Raw captures from Tree-sitter query
216
- source_code: Source code for context
217
-
218
- Returns:
219
- List of processed capture dictionaries
220
- """
221
- processed = []
222
-
223
- try:
224
- for capture in captures:
225
- try:
226
- # Handle both dictionary and tuple formats
227
- if isinstance(capture, dict):
228
- # New Tree-sitter API format
229
- node = capture.get("node")
230
- name = capture.get("name", "unknown")
231
- elif isinstance(capture, tuple) and len(capture) == 2:
232
- # Old Tree-sitter API format
233
- node, name = capture
234
- else:
235
- logger.warning(f"Unexpected capture format: {type(capture)}")
236
- continue
237
-
238
- if node is None:
239
- continue
240
-
241
- result_dict = self._create_result_dict(node, name, source_code)
242
- processed.append(result_dict)
243
-
244
- except Exception as e:
245
- logger.error(f"Error processing capture: {e}")
246
- continue
247
-
248
- except Exception as e:
249
- logger.error(f"Error in _process_captures: {e}")
250
-
251
- return processed
252
-
253
- def _create_result_dict(
254
- self, node: Node, capture_name: str, source_code: str
255
- ) -> dict[str, Any]:
256
- """
257
- Create a result dictionary from a Tree-sitter node.
258
-
259
- Args:
260
- node: Tree-sitter node
261
- capture_name: Name of the capture
262
- source_code: Source code for context
263
-
264
- Returns:
265
- Dictionary containing node information
266
- """
267
- try:
268
- # Extract node text
269
- node_text = ""
270
- if hasattr(node, "text") and node.text:
271
- try:
272
- node_text = node.text.decode("utf-8", errors="replace")
273
- except Exception:
274
- node_text = str(node.text)
275
-
276
- return {
277
- "capture_name": capture_name,
278
- "node_type": getattr(node, "type", "unknown"),
279
- "start_point": getattr(node, "start_point", (0, 0)),
280
- "end_point": getattr(node, "end_point", (0, 0)),
281
- "start_byte": getattr(node, "start_byte", 0),
282
- "end_byte": getattr(node, "end_byte", 0),
283
- "text": node_text,
284
- "line_number": getattr(node, "start_point", (0, 0))[0] + 1,
285
- "column_number": getattr(node, "start_point", (0, 0))[1],
286
- }
287
-
288
- except Exception as e:
289
- logger.error(f"Error creating result dict: {e}")
290
- return {"capture_name": capture_name, "node_type": "error", "error": str(e)}
291
-
292
- def _create_error_result(
293
- self, error_message: str, query_name: str | None = None, **kwargs: Any
294
- ) -> dict[str, Any]:
295
- """
296
- Create an error result dictionary.
297
-
298
- Args:
299
- error_message: Error message
300
- query_name: Optional query name
301
- **kwargs: Additional fields to include in the error result
302
-
303
- Returns:
304
- Error result dictionary
305
- """
306
- result = {"captures": [], "error": error_message, "success": False}
307
-
308
- if query_name:
309
- result["query_name"] = query_name
310
-
311
- result.update(kwargs)
312
- return result
313
-
314
- def get_available_queries(self, language: str) -> list[str]:
315
- """
316
- Get available queries for a language.
317
-
318
- Args:
319
- language: Programming language name
320
-
321
- Returns:
322
- List of available query names
323
- """
324
- try:
325
- queries = self._query_loader.get_all_queries_for_language(language)
326
- if isinstance(queries, dict):
327
- return list(queries.keys())
328
- # Handle other iterable types
329
- try: # type: ignore[unreachable]
330
- return list(queries) if queries else []
331
- except (TypeError, ValueError):
332
- return []
333
- except Exception as e:
334
- logger.error(f"Error getting available queries for {language}: {e}")
335
- return []
336
-
337
- def get_query_description(self, language: str, query_name: str) -> str | None:
338
- """
339
- Get description for a specific query.
340
-
341
- Args:
342
- language: Programming language name
343
- query_name: Name of the query
344
-
345
- Returns:
346
- Query description or None if not found
347
- """
348
- try:
349
- return self._query_loader.get_query_description(language, query_name)
350
- except Exception as e:
351
- logger.error(f"Error getting query description: {e}")
352
- return None
353
-
354
- def validate_query(self, language: str, query_string: str) -> bool:
355
- """
356
- Validate a query string for a specific language.
357
-
358
- Args:
359
- language: Programming language name
360
- query_string: Query string to validate
361
-
362
- Returns:
363
- True if query is valid, False otherwise
364
- """
365
- try:
366
- # This would require loading the language and attempting to create the query
367
- # For now, we'll do basic validation
368
- from ..language_loader import get_loader
369
-
370
- loader = get_loader()
371
-
372
- lang_obj = loader.load_language(language)
373
- if lang_obj is None:
374
- return False
375
-
376
- # Try to create the query
377
- lang_obj.query(query_string)
378
- return True
379
-
380
- except Exception as e:
381
- logger.error(f"Query validation failed: {e}")
382
- return False
383
-
384
- def get_query_statistics(self) -> dict[str, Any]:
385
- """
386
- Get query execution statistics.
387
-
388
- Returns:
389
- Dictionary containing execution statistics
390
- """
391
- stats = self._execution_stats.copy()
392
-
393
- if stats["total_queries"] > 0:
394
- stats["success_rate"] = stats["successful_queries"] / stats["total_queries"]
395
- stats["average_execution_time"] = (
396
- stats["total_execution_time"] / stats["total_queries"]
397
- )
398
- else:
399
- stats["success_rate"] = 0.0
400
- stats["average_execution_time"] = 0.0
401
-
402
- return stats
403
-
404
- def reset_statistics(self) -> None:
405
- """Reset query execution statistics."""
406
- self._execution_stats = {
407
- "total_queries": 0,
408
- "successful_queries": 0,
409
- "failed_queries": 0,
410
- "total_execution_time": 0.0,
411
- }
412
-
413
-
414
- # Module-level convenience functions for backward compatibility
415
- def get_available_queries(language: str | None = None) -> list[str]:
416
- """
417
- Get available queries for a language (module-level function).
418
-
419
- Args:
420
- language: Programming language name (optional)
421
-
422
- Returns:
423
- List of available query names
424
- """
425
- try:
426
- loader = get_query_loader()
427
- if language:
428
- return loader.list_queries_for_language(language)
429
-
430
- # If no language, return a list of all query names across supported languages
431
- all_queries = set()
432
- for lang in loader.list_supported_languages():
433
- all_queries.update(loader.list_queries_for_language(lang))
434
- return sorted(all_queries)
435
-
436
- except Exception as e:
437
- logger.error(f"Error getting available queries: {e}")
438
- return []
439
-
440
-
441
- def get_query_description(language: str, query_name: str) -> str | None:
442
- """
443
- Get description for a specific query (module-level function).
444
-
445
- Args:
446
- language: Programming language name
447
- query_name: Name of the query
448
-
449
- Returns:
450
- Query description or None if not found
451
- """
452
- try:
453
- from ..query_loader import get_query_loader
454
-
455
- loader = get_query_loader()
456
- return loader.get_query_description(language, query_name)
457
- except Exception as e:
458
- logger.error(f"Error getting query description: {e}")
459
- return None
460
-
461
-
462
- # Module-level attributes for backward compatibility
463
- try:
464
- from ..query_loader import get_query_loader
465
-
466
- query_loader = get_query_loader()
467
- except Exception:
468
- query_loader = None # type: ignore
469
-
470
-
471
- def get_all_queries_for_language(language: str) -> list[str]:
472
- """
473
- Get all available queries for a specific language.
474
-
475
- Args:
476
- language: Programming language name
477
-
478
- Returns:
479
- List of available query names for the language
480
-
481
- .. deprecated:: 0.2.1
482
- This function is deprecated and will be removed in a future version.
483
- Use the unified analysis engine instead.
484
- """
485
- import warnings
486
-
487
- warnings.warn(
488
- "get_all_queries_for_language is deprecated and will be removed in a future version. "
489
- "Use the unified analysis engine instead.",
490
- DeprecationWarning,
491
- stacklevel=2,
492
- )
493
- return []
494
-
495
-
496
- # Update module-level attributes for backward compatibility
497
- try:
498
- from ..language_loader import get_loader
499
-
500
- loader = get_loader()
501
- except Exception:
502
- loader = None # type: ignore
1
+ #!/usr/bin/env python3
2
+ """
3
+ Query module for tree_sitter_analyzer.core.
4
+
5
+ This module provides the QueryExecutor class which handles Tree-sitter
6
+ query execution in the new architecture.
7
+ """
8
+
9
+ import logging
10
+ import time
11
+ from typing import Any
12
+
13
+ from tree_sitter import Language, Node, Tree
14
+
15
+ from ..query_loader import get_query_loader
16
+
17
+ # Configure logging
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class QueryExecutor:
22
+ """
23
+ Tree-sitter query executor for the new architecture.
24
+
25
+ This class provides a unified interface for executing Tree-sitter queries
26
+ with proper error handling and result processing.
27
+ """
28
+
29
+ def __init__(self) -> None:
30
+ """Initialize the QueryExecutor."""
31
+ self._query_loader = get_query_loader()
32
+ self._execution_stats: dict[str, Any] = {
33
+ "total_queries": 0,
34
+ "successful_queries": 0,
35
+ "failed_queries": 0,
36
+ "total_execution_time": 0.0,
37
+ }
38
+ logger.info("QueryExecutor initialized successfully")
39
+
40
+ def execute_query(
41
+ self,
42
+ tree: Tree | None,
43
+ language: Language,
44
+ query_name: str,
45
+ source_code: str,
46
+ ) -> dict[str, Any]:
47
+ """
48
+ Execute a predefined query by name.
49
+
50
+ Args:
51
+ tree: Tree-sitter tree to query
52
+ language: Tree-sitter language object
53
+ query_name: Name of the predefined query
54
+ source_code: Source code for context
55
+
56
+ Returns:
57
+ Dictionary containing query results and metadata
58
+ """
59
+ start_time = time.time()
60
+ self._execution_stats["total_queries"] += 1
61
+
62
+ try:
63
+ # Validate inputs
64
+ if tree is None:
65
+ return self._create_error_result("Tree is None", query_name=query_name)
66
+
67
+ if language is None:
68
+ return self._create_error_result( # type: ignore[unreachable]
69
+ "Language is None", query_name=query_name
70
+ )
71
+
72
+ # Get the query string
73
+ language_name = language.name if hasattr(language, "name") else "unknown"
74
+ query_string = self._query_loader.get_query(language_name, query_name)
75
+ if query_string is None:
76
+ return self._create_error_result(
77
+ f"Query '{query_name}' not found", query_name=query_name
78
+ )
79
+
80
+ # Create and execute the query
81
+ try:
82
+ query = language.query(query_string)
83
+ captures = query.captures(tree.root_node)
84
+
85
+ # Process captures
86
+ try:
87
+ processed_captures = self._process_captures(captures, source_code)
88
+ except Exception as e:
89
+ logger.error(f"Error processing captures for {query_name}: {e}")
90
+ return self._create_error_result(
91
+ f"Capture processing failed: {str(e)}", query_name=query_name
92
+ )
93
+
94
+ self._execution_stats["successful_queries"] += 1
95
+ execution_time = time.time() - start_time
96
+ self._execution_stats["total_execution_time"] += execution_time
97
+
98
+ return {
99
+ "captures": processed_captures,
100
+ "query_name": query_name,
101
+ "query_string": query_string,
102
+ "execution_time": execution_time,
103
+ "success": True,
104
+ }
105
+
106
+ except Exception as e:
107
+ logger.error(f"Error executing query '{query_name}': {e}")
108
+ return self._create_error_result(
109
+ f"Query execution failed: {str(e)}", query_name=query_name
110
+ )
111
+
112
+ except Exception as e:
113
+ logger.error(f"Unexpected error in execute_query: {e}")
114
+ self._execution_stats["failed_queries"] += 1
115
+ return self._create_error_result(
116
+ f"Unexpected error: {str(e)}", query_name=query_name
117
+ )
118
+
119
+ def execute_query_string(
120
+ self,
121
+ tree: Tree | None,
122
+ language: Language,
123
+ query_string: str,
124
+ source_code: str,
125
+ ) -> dict[str, Any]:
126
+ """
127
+ Execute a query string directly.
128
+
129
+ Args:
130
+ tree: Tree-sitter tree to query
131
+ language: Tree-sitter language object
132
+ query_string: Query string to execute
133
+ source_code: Source code for context
134
+
135
+ Returns:
136
+ Dictionary containing query results and metadata
137
+ """
138
+ start_time = time.time()
139
+ self._execution_stats["total_queries"] += 1
140
+
141
+ try:
142
+ # Validate inputs
143
+ if tree is None:
144
+ return self._create_error_result("Tree is None")
145
+
146
+ if language is None:
147
+ return self._create_error_result("Language is None") # type: ignore[unreachable]
148
+
149
+ # Create and execute the query
150
+ try:
151
+ query = language.query(query_string)
152
+ captures = query.captures(tree.root_node)
153
+
154
+ # Process captures
155
+ try:
156
+ processed_captures = self._process_captures(captures, source_code)
157
+ except Exception as e:
158
+ logger.error(f"Error processing captures: {e}")
159
+ return self._create_error_result(
160
+ f"Capture processing failed: {str(e)}"
161
+ )
162
+
163
+ self._execution_stats["successful_queries"] += 1
164
+ execution_time = time.time() - start_time
165
+ self._execution_stats["total_execution_time"] += execution_time
166
+
167
+ return {
168
+ "captures": processed_captures,
169
+ "query_string": query_string,
170
+ "execution_time": execution_time,
171
+ "success": True,
172
+ }
173
+
174
+ except Exception as e:
175
+ logger.error(f"Error executing query string: {e}")
176
+ return self._create_error_result(
177
+ f"Query execution failed: {str(e)}", query_string=query_string
178
+ )
179
+
180
+ except Exception as e:
181
+ logger.error(f"Unexpected error in execute_query_string: {e}")
182
+ self._execution_stats["failed_queries"] += 1
183
+ return self._create_error_result(f"Unexpected error: {str(e)}")
184
+
185
+ def execute_multiple_queries(
186
+ self, tree: Tree, language: Language, query_names: list[str], source_code: str
187
+ ) -> dict[str, dict[str, Any]]:
188
+ """
189
+ Execute multiple queries and return combined results.
190
+
191
+ Args:
192
+ tree: Tree-sitter tree to query
193
+ language: Tree-sitter language object
194
+ query_names: List of query names to execute
195
+ source_code: Source code for context
196
+
197
+ Returns:
198
+ Dictionary mapping query names to their results
199
+ """
200
+ results = {}
201
+
202
+ for query_name in query_names:
203
+ result = self.execute_query(tree, language, query_name, source_code)
204
+ results[query_name] = result
205
+
206
+ return results
207
+
208
+ def _process_captures(
209
+ self, captures: Any, source_code: str
210
+ ) -> list[dict[str, Any]]:
211
+ """
212
+ Process query captures into standardized format.
213
+
214
+ Args:
215
+ captures: Raw captures from Tree-sitter query
216
+ source_code: Source code for context
217
+
218
+ Returns:
219
+ List of processed capture dictionaries
220
+ """
221
+ processed = []
222
+
223
+ try:
224
+ for capture in captures:
225
+ try:
226
+ # Handle both dictionary and tuple formats
227
+ if isinstance(capture, dict):
228
+ # New Tree-sitter API format
229
+ node = capture.get("node")
230
+ name = capture.get("name", "unknown")
231
+ elif isinstance(capture, tuple) and len(capture) == 2:
232
+ # Old Tree-sitter API format
233
+ node, name = capture
234
+ else:
235
+ logger.warning(f"Unexpected capture format: {type(capture)}")
236
+ continue
237
+
238
+ if node is None:
239
+ continue
240
+
241
+ result_dict = self._create_result_dict(node, name, source_code)
242
+ processed.append(result_dict)
243
+
244
+ except Exception as e:
245
+ logger.error(f"Error processing capture: {e}")
246
+ continue
247
+
248
+ except Exception as e:
249
+ logger.error(f"Error in _process_captures: {e}")
250
+
251
+ return processed
252
+
253
+ def _create_result_dict(
254
+ self, node: Node, capture_name: str, source_code: str
255
+ ) -> dict[str, Any]:
256
+ """
257
+ Create a result dictionary from a Tree-sitter node.
258
+
259
+ Args:
260
+ node: Tree-sitter node
261
+ capture_name: Name of the capture
262
+ source_code: Source code for context
263
+
264
+ Returns:
265
+ Dictionary containing node information
266
+ """
267
+ try:
268
+ # Extract node text
269
+ node_text = ""
270
+ if hasattr(node, "text") and node.text:
271
+ try:
272
+ node_text = node.text.decode("utf-8", errors="replace")
273
+ except Exception:
274
+ node_text = str(node.text)
275
+
276
+ return {
277
+ "capture_name": capture_name,
278
+ "node_type": getattr(node, "type", "unknown"),
279
+ "start_point": getattr(node, "start_point", (0, 0)),
280
+ "end_point": getattr(node, "end_point", (0, 0)),
281
+ "start_byte": getattr(node, "start_byte", 0),
282
+ "end_byte": getattr(node, "end_byte", 0),
283
+ "text": node_text,
284
+ "line_number": getattr(node, "start_point", (0, 0))[0] + 1,
285
+ "column_number": getattr(node, "start_point", (0, 0))[1],
286
+ }
287
+
288
+ except Exception as e:
289
+ logger.error(f"Error creating result dict: {e}")
290
+ return {"capture_name": capture_name, "node_type": "error", "error": str(e)}
291
+
292
+ def _create_error_result(
293
+ self, error_message: str, query_name: str | None = None, **kwargs: Any
294
+ ) -> dict[str, Any]:
295
+ """
296
+ Create an error result dictionary.
297
+
298
+ Args:
299
+ error_message: Error message
300
+ query_name: Optional query name
301
+ **kwargs: Additional fields to include in the error result
302
+
303
+ Returns:
304
+ Error result dictionary
305
+ """
306
+ result = {"captures": [], "error": error_message, "success": False}
307
+
308
+ if query_name:
309
+ result["query_name"] = query_name
310
+
311
+ result.update(kwargs)
312
+ return result
313
+
314
+ def get_available_queries(self, language: str) -> list[str]:
315
+ """
316
+ Get available queries for a language.
317
+
318
+ Args:
319
+ language: Programming language name
320
+
321
+ Returns:
322
+ List of available query names
323
+ """
324
+ try:
325
+ queries = self._query_loader.get_all_queries_for_language(language)
326
+ if isinstance(queries, dict):
327
+ return list(queries.keys())
328
+ # Handle other iterable types
329
+ try: # type: ignore[unreachable]
330
+ return list(queries) if queries else []
331
+ except (TypeError, ValueError):
332
+ return []
333
+ except Exception as e:
334
+ logger.error(f"Error getting available queries for {language}: {e}")
335
+ return []
336
+
337
+ def get_query_description(self, language: str, query_name: str) -> str | None:
338
+ """
339
+ Get description for a specific query.
340
+
341
+ Args:
342
+ language: Programming language name
343
+ query_name: Name of the query
344
+
345
+ Returns:
346
+ Query description or None if not found
347
+ """
348
+ try:
349
+ return self._query_loader.get_query_description(language, query_name)
350
+ except Exception as e:
351
+ logger.error(f"Error getting query description: {e}")
352
+ return None
353
+
354
+ def validate_query(self, language: str, query_string: str) -> bool:
355
+ """
356
+ Validate a query string for a specific language.
357
+
358
+ Args:
359
+ language: Programming language name
360
+ query_string: Query string to validate
361
+
362
+ Returns:
363
+ True if query is valid, False otherwise
364
+ """
365
+ try:
366
+ # This would require loading the language and attempting to create the query
367
+ # For now, we'll do basic validation
368
+ from ..language_loader import get_loader
369
+
370
+ loader = get_loader()
371
+
372
+ lang_obj = loader.load_language(language)
373
+ if lang_obj is None:
374
+ return False
375
+
376
+ # Try to create the query
377
+ lang_obj.query(query_string)
378
+ return True
379
+
380
+ except Exception as e:
381
+ logger.error(f"Query validation failed: {e}")
382
+ return False
383
+
384
+ def get_query_statistics(self) -> dict[str, Any]:
385
+ """
386
+ Get query execution statistics.
387
+
388
+ Returns:
389
+ Dictionary containing execution statistics
390
+ """
391
+ stats = self._execution_stats.copy()
392
+
393
+ if stats["total_queries"] > 0:
394
+ stats["success_rate"] = stats["successful_queries"] / stats["total_queries"]
395
+ stats["average_execution_time"] = (
396
+ stats["total_execution_time"] / stats["total_queries"]
397
+ )
398
+ else:
399
+ stats["success_rate"] = 0.0
400
+ stats["average_execution_time"] = 0.0
401
+
402
+ return stats
403
+
404
+ def reset_statistics(self) -> None:
405
+ """Reset query execution statistics."""
406
+ self._execution_stats = {
407
+ "total_queries": 0,
408
+ "successful_queries": 0,
409
+ "failed_queries": 0,
410
+ "total_execution_time": 0.0,
411
+ }
412
+
413
+
414
+ # Module-level convenience functions for backward compatibility
415
+ def get_available_queries(language: str | None = None) -> list[str]:
416
+ """
417
+ Get available queries for a language (module-level function).
418
+
419
+ Args:
420
+ language: Programming language name (optional)
421
+
422
+ Returns:
423
+ List of available query names
424
+ """
425
+ try:
426
+ loader = get_query_loader()
427
+ if language:
428
+ return loader.list_queries_for_language(language)
429
+
430
+ # If no language, return a list of all query names across supported languages
431
+ all_queries = set()
432
+ for lang in loader.list_supported_languages():
433
+ all_queries.update(loader.list_queries_for_language(lang))
434
+ return sorted(all_queries)
435
+
436
+ except Exception as e:
437
+ logger.error(f"Error getting available queries: {e}")
438
+ return []
439
+
440
+
441
+ def get_query_description(language: str, query_name: str) -> str | None:
442
+ """
443
+ Get description for a specific query (module-level function).
444
+
445
+ Args:
446
+ language: Programming language name
447
+ query_name: Name of the query
448
+
449
+ Returns:
450
+ Query description or None if not found
451
+ """
452
+ try:
453
+ from ..query_loader import get_query_loader
454
+
455
+ loader = get_query_loader()
456
+ return loader.get_query_description(language, query_name)
457
+ except Exception as e:
458
+ logger.error(f"Error getting query description: {e}")
459
+ return None
460
+
461
+
462
+ # Module-level attributes for backward compatibility
463
+ try:
464
+ from ..query_loader import get_query_loader
465
+
466
+ query_loader = get_query_loader()
467
+ except Exception:
468
+ query_loader = None # type: ignore
469
+
470
+
471
+ def get_all_queries_for_language(language: str) -> list[str]:
472
+ """
473
+ Get all available queries for a specific language.
474
+
475
+ Args:
476
+ language: Programming language name
477
+
478
+ Returns:
479
+ List of available query names for the language
480
+
481
+ .. deprecated:: 0.2.1
482
+ This function is deprecated and will be removed in a future version.
483
+ Use the unified analysis engine instead.
484
+ """
485
+ import warnings
486
+
487
+ warnings.warn(
488
+ "get_all_queries_for_language is deprecated and will be removed in a future version. "
489
+ "Use the unified analysis engine instead.",
490
+ DeprecationWarning,
491
+ stacklevel=2,
492
+ )
493
+ return []
494
+
495
+
496
+ # Update module-level attributes for backward compatibility
497
+ try:
498
+ from ..language_loader import get_loader
499
+
500
+ loader = get_loader()
501
+ except Exception:
502
+ loader = None # type: ignore