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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. 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)