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